/**
 * 
 */
package jvs.vfs.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author qiangli
 * 
 */
public class ChunkedInputStream extends InputStream {
	private static final int CR = 13;
	private static final int LF = 10;

	private InputStream in;
	private int pos;
	private int chunkSize;
	private boolean bos;
	private boolean eos;
	private boolean closed;

	/**
	 * 
	 */
	public ChunkedInputStream(InputStream in) {
		this.in = in;
		this.bos = true;
		this.eos = false;
		this.closed = false;
		this.pos = 0;
		this.chunkSize = -1;
	}

	@Override
	public int read() throws IOException {
		if (closed) {
			throw new IOException("Stream closed.");
		}
		if (eos) {
			return -1;
		}
		if (pos >= chunkSize) {
			readChunk();
			if (eos) {
				return -1;
			}
		}
		int b = in.read();
		if (b != -1) {
			pos++;
		}
		return b;
	}

	@Override
	public int available() throws IOException {
		return 0;
	}

	@Override
	public void close() throws IOException {
		in.close();
		closed = true;
	}

	@Override
	public boolean markSupported() {
		return false;
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		if (closed) {
			throw new IOException("Stream closed.");
		}
		if (eos) {
			return -1;
		}
		if (pos >= chunkSize) {
			readChunk();
			if (eos) {
				return -1;
			}
		}
		len = Math.min(len, chunkSize - pos);
		int cnt = in.read(b, off, len);
		if (cnt == -1) {
			throw new IOException("Invalid chunk.");
		}
		pos += cnt;
		return cnt;
	}

	@Override
	public int read(byte[] b) throws IOException {
		return read(b, 0, b.length);
	}

	@Override
	public long skip(long n) throws IOException {
		throw new IOException("Not supported");
	}

	private void readChunk() throws IOException {
		readChunkSize();
		pos = 0;
		if (chunkSize == 0) {
			eos = true;
			skipTrailer();
		}
	}

	private void skipTrailer() throws IOException {
		while ((skipLine() > 0)) {
			;
		}
	}

	private int skipLine() throws IOException {
		int cnt = 0;
		while (true) {
			int c = in.read();
			if (c == CR) {
				c = in.read();
				if (c == LF) {
					return cnt;
				}
			} else {
				cnt++;
			}
		}
	}
	
	private void readChunkSize() throws IOException {
		byte[] b = new byte[8];
		int i = 0;
		if (!bos) {
			//skip CRLF at end of data 
			in.read();
			in.read();
		}
		bos = false;
		for (; i < b.length; i++) {
			b[i] = (byte) in.read();
			if (b[i] == ';' || b[i] == CR) {
				break;
			}
		}
		while ((in.read() != LF)) {
			;
		}
		String hex = new String(b, 0, i);
		chunkSize = Integer.parseInt(hex, 16);
		if (chunkSize < 0) {
			throw new IOException("Invalid chunk size");
		}
	}
}
